package com.xiaode.util.apidoc;

import com.jfinal.core.Controller;
import com.jfinal.core.Path;
import com.xiaode.util.apidoc.annotation.Api;
import com.xiaode.util.apidoc.annotation.ApiOper;

import cn.hutool.core.lang.ClassScanner;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;

public class ApiDocManager {

    private static final ApiDocManager me = new ApiDocManager();

    public static ApiDocManager me() {
        return me;
    }

    private ApiDocRender render = ApiDocRender.DEFAULT_RENDER;

    public ApiDocRender getRender() {
        return render;
    }

    public void setRender(ApiDocRender render) {
        this.render = render;
    }

    /**
     * 生成 API 文档
     *
     * @param config
     */
    public void genDocs(ApiDocConfig config) {
    	
//    	Controller.class.isAssignableFrom(aClass)
//    	
//    	aClass -> Controller.class.isAssignableFrom(aClass) && aClass.getAnnotation(Api.class) != null;
    	
        Set<Class<?>> controllerClasses = ClassScanner.scanPackage("com", aClass -> Controller.class.isAssignableFrom(aClass) && aClass.getAnnotation(Api.class) != null);
        if (controllerClasses.isEmpty()) {
            return;
        }

        List<ApiDocument> apiDocuments = new ArrayList<>();
        for (Class<?> controllerClass : controllerClasses) {
            if (StrUtil.isNotBlank(config.getPackagePrefix())) {
                if (controllerClass.getName().startsWith(config.getPackagePrefix())) {
                    apiDocuments.add(buildDocument(controllerClass));
                }
            } else {
                apiDocuments.add(buildDocument(controllerClass));
            }
        }

        if (render != null) {
            render.render(apiDocuments, config);
        }
    }

    private ApiDocument buildDocument(Class<?> controllerClass) {

        Api api = controllerClass.getAnnotation(Api.class);
        ApiDocument document = new ApiDocument();
        document.setControllerClass(controllerClass);
        document.setValue(api.value());
        document.setNotes(api.notes());

        setDocmentPathAndMethod(document, controllerClass);

        String filePath = api.filePath();
        if (StrUtil.isNotBlank(filePath)) {
            document.setFilePath(filePath);
        }

        List<Method> methods = ReflectUtil.getPublicMethods(controllerClass, method -> method.getAnnotation(ApiOper.class) != null && Modifier.isPublic(method.getModifiers()));

        for (Method method : methods) {
            ApiOperation apiOperation = new ApiOperation();
            apiOperation.setMethodAndInfo(method, document.getControllerPath(), getMethodHttpMethods(method, document.getControllerMethod()));

            ApiOper apiOper = method.getAnnotation(ApiOper.class);
            apiOperation.setValue(apiOper.value());
            apiOperation.setNotes(apiOper.notes());
            apiOperation.setParaNotes(apiOper.paraNotes());
            apiOperation.setOrderNo(apiOper.orderNo());
            apiOperation.setContentType(apiOper.contentType());

            document.addOperation(apiOperation);
        }


        Class<?>[] collectClasses = api.collect();
        for (Class<?> cClass : collectClasses) {

            ApiDocument temp = new ApiDocument();
            temp.setControllerClass(cClass);
            setDocmentPathAndMethod(temp, cClass);

            List<Method> collectMethods = ReflectUtil.getPublicMethods(cClass, method -> method.getAnnotation(ApiOper.class) != null && Modifier.isPublic(method.getModifiers()));

            for (Method method : collectMethods) {
                ApiOperation apiOperation = new ApiOperation();
                apiOperation.setMethodAndInfo(method, temp.getControllerPath(), getMethodHttpMethods(method, temp.getControllerMethod()));

                ApiOper apiOper = method.getAnnotation(ApiOper.class);
                apiOperation.setValue(apiOper.value());
                apiOperation.setNotes(apiOper.notes());
                apiOperation.setParaNotes(apiOper.paraNotes());
                apiOperation.setOrderNo(apiOper.orderNo());
                apiOperation.setContentType(apiOper.contentType());

                document.addOperation(apiOperation);
            }
        }



        List<ApiOperation> operations = document.getApiOperations();
        if (operations != null) {
            operations.sort(new Comparator<ApiOperation>() {
                @Override
                public int compare(ApiOperation o1, ApiOperation o2) {
                    return o1.getOrderNo() == o2.getOrderNo() ? o1.getMethod().getName().compareTo(o2.getMethod().getName()) : o1.getOrderNo() - o2.getOrderNo();
                }
            });
        }

        return document;
    }


    private void setDocmentPathAndMethod(ApiDocument docment, Class<?> controllerClass) {
        
    	Path path = controllerClass.getAnnotation(Path.class);
        if (path != null) {
            docment.setMappingAndFilePath(path.value());
            docment.setControllerMethod(HttpMethod.ALL);
            return;
        }
    	
    	
//    	RequestMapping rm = controllerClass.getAnnotation(RequestMapping.class);
//        if (rm != null) {
//            docment.setMappingAndFilePath(rm.value());
//            docment.setControllerMethod(HttpMethod.ALL);
//            return;
//        }
//
//        PostMapping pm = controllerClass.getAnnotation(PostMapping.class);
//        if (pm != null) {
//            docment.setMappingAndFilePath(pm.value());
//            docment.setControllerMethod(HttpMethod.POST);
//            return;
//        }
//
//        GetMapping gm = controllerClass.getAnnotation(GetMapping.class);
//        if (gm != null) {
//            docment.setMappingAndFilePath(gm.value());
//            docment.setControllerMethod(HttpMethod.GET);
//            return;
//        }
    }

    private HttpMethod[] getMethodHttpMethods(Method method, HttpMethod defaultMethod) {
        Set<HttpMethod> httpMethods = new HashSet<>();
        if (method.getAnnotation(Path.class) != null) {
            httpMethods.add(HttpMethod.POST);
        }
        return httpMethods.isEmpty() ? new HttpMethod[]{defaultMethod} : httpMethods.toArray(new HttpMethod[]{});
    }


    public static void main(String[] args) {

        ApiDocConfig config = new ApiDocConfig();
        config.setBasePath("./doc/api");

        ApiDocManager.me().genDocs(config);
    }
    
    
    
    


}
